// Copyright 2006, 2007 Dennis van Weeren
//
// This file is part of Minimig
//
// Minimig is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Minimig is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//
//
// This is the audio part of Paula
//
// 27-12-2005		-started coding
// 28-12-2005		-done lots of work
// 29-12-2005		-done lots of work
// 01-01-2006		-we are having OK sound in dma mode now
// 02-01-2006		-fixed last state
// 03-01-2006		-added dmas to avoid interference with copper cycles
// 04-01-2006		-experimented with DAC
// 06-01-2006		-experimented some more with DAC and decided to leave it as it is for now
// 07-01-2006		-cleaned up code
// 21-02-2006		-improved audio state machine
// 22-02-2006		-fixed dma interrupt timing, Turrican-3 theme now plays correct!
//
// -- JB --
// 2008-10-12	- code clean-up
// 2008-12-20	- changed DMA slot allocation
// 2009-03-08	- horbeam removed
//				- strhor signal added (cures problems with freezing of some games)
//				- corrupted Agony title song
// 2009-03-17	- audio FSM rewritten to comply more exactly with HRM state diagram, Agony still has problems
// 2009-03-26	- audio dma requests are latched and cleared at the start of every scan line, seemd to cure Agony problem
//				- Forgotten Worlds freezes at game intro screen due to missed audio irq
// 2009-05-24	- clean-up & renaming



// Paula requests data from Agnus using DMAL line (high active state)
// DMAL time slot allocation: (relative to first refresh slot referenced as $00)
// $03,$05,$07 - all these slots are active when disk dma is inactive or there is no data in disk buffer to transfer
// $04 - disk buffer has at least 3 words to transfer (transfer takes place in $08)
// $06 - disk buffer has at least 2 words to transfer (transfer takes place in $0A)
// $08 - disk buffer has at least 1 word to transfer (transfer takes place in $0C)
// $09 - audio channel #0 location pointer reload request (active with data request) 
// $0A - audio channle #0 dma data request (data transfered in slot $0E)
// $0B - audio channel #1 location pointer reload request (active with data request) 
// $0C - audio channle #1 dma data request (data transfered in slot $10)
// $0D - audio channel #2 location pointer reload request (active with data request) 
// $0E - audio channle #2 dma data request (data transfered in slot $12)
// $0F - audio channel #3 location pointer reload request (active with data request) 
// $10 - audio channle #3 dma data request (data transfered in slot $14)
// minimum sampling period for audio channels (in CCK's):
// #0 : 121/120
// #1 : 122/121
// #2 : 123/122
// #3 : 124/123


module audio
(
	input 	clk,		    		//bus clock
	input 	cck,		    		//colour clock enable
	input 	reset,			   		//reset 
	input	strhor,					//horizontal strobe
	input 	[8:1] reg_address_in,	//register address input
	input	[15:0] data_in,			//bus data in
	input	[3:0] dmaena,			//audio dma register input
	output	[3:0] audint,			//audio interrupt request
	input	[3:0] audpen,			//audio interrupt pending
	output	reg [3:0] dmal,			//dma request 
	output	reg [3:0] dmas,			//dma special 
	output	left,					//audio bitstream out left
	output	right					//audio bitstream out right
);

//register names and addresses
parameter	AUD0BASE = 9'h0a0;
parameter	AUD1BASE = 9'h0b0;
parameter	AUD2BASE = 9'h0c0;
parameter	AUD3BASE = 9'h0d0;

//local signals 
wire	[3:0] aen;			//address enable 0-3
wire	[3:0] dmareq;		//dma request 0-3
wire	[3:0] dmaspc;		//dma restart 0-3
wire	[7:0] sample0;		//channel 0 audio sample 
wire	[7:0] sample1;		//channel 1 audio sample 
wire	[7:0] sample2;		//channel 2 audio sample 
wire	[7:0] sample3;		//channel 3 audio sample 
wire	[6:0] vol0;			//channel 0 volume 
wire	[6:0] vol1;			//channel 1 volume 
wire	[6:0] vol2;			//channel 2 volume 
wire	[6:0] vol3;			//channel 3 volume 

//--------------------------------------------------------------------------------------

//address decoder
assign aen[0] = (reg_address_in[8:4]==AUD0BASE[8:4]) ? 1 : 0;
assign aen[1] = (reg_address_in[8:4]==AUD1BASE[8:4]) ? 1 : 0;
assign aen[2] = (reg_address_in[8:4]==AUD2BASE[8:4]) ? 1 : 0;
assign aen[3] = (reg_address_in[8:4]==AUD3BASE[8:4]) ? 1 : 0;

//--------------------------------------------------------------------------------------

//DMA slot allocation is managed by Agnus 
//#0 : 0E
//#1 : 10
//#2 : 12
//#3 : 14

always @(posedge clk)
	if (strhor)
	begin
		dmal <= dmareq;
		dmas <= dmaspc;
	end
		
//--------------------------------------------------------------------------------------

//instantiate audio channel 0
audiochannel ach0
(
	.clk(clk),
	.reset(reset),
	.cck(cck),
	.aen(aen[0]),
	.dmaena(dmaena[0]),
	.reg_address_in(reg_address_in[3:1]),
	.data(data_in),
	.volume(vol0),
	.sample(sample0),
	.intreq(audint[0]),
	.intpen(audpen[0]),
	.dmareq(dmareq[0]),
	.dmas(dmaspc[0]),
	.strhor(strhor)
);

//instantiate audio channel 1
audiochannel ach1
(
	.clk(clk),
	.reset(reset),
	.cck(cck),
	.aen(aen[1]),
	.dmaena(dmaena[1]),
	.reg_address_in(reg_address_in[3:1]),
	.data(data_in),
	.volume(vol1),
	.sample(sample1),
	.intreq(audint[1]),
	.intpen(audpen[1]),
	.dmareq(dmareq[1]),
	.dmas(dmaspc[1]),
	.strhor(strhor)
);

//instantiate audio channel 2
audiochannel ach2 
(	
	.clk(clk),
	.reset(reset),
	.cck(cck),
	.aen(aen[2]),
	.dmaena(dmaena[2]),
	.reg_address_in(reg_address_in[3:1]),
	.data(data_in),
	.volume(vol2),
	.sample(sample2),
	.intreq(audint[2]),
	.intpen(audpen[2]),
	.dmareq(dmareq[2]),
	.dmas(dmaspc[2]),
	.strhor(strhor)	
);

//instantiate audio channel 3
audiochannel ach3
(		
	.clk(clk),
	.reset(reset),
	.cck(cck),
	.aen(aen[3]),
	.dmaena(dmaena[3]),
	.reg_address_in(reg_address_in[3:1]),
	.data(data_in),
	.volume(vol3),
	.sample(sample3),
	.intreq(audint[3]),
	.intpen(audpen[3]),
	.dmareq(dmareq[3]),
	.dmas(dmaspc[3]),
	.strhor(strhor)
);

//instantiate volume control and sigma/delta modulator
sigmadelta dac0 
(
	.clk(clk),
	.sample0(sample0),
	.sample1(sample1),
	.sample2(sample2),
	.sample3(sample3),
	.vol0(vol0),
	.vol1(vol1),
	.vol2(vol2),
	.vol3(vol3),
	.left(left),
	.right(right)	
);

//--------------------------------------------------------------------------------------

endmodule

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------

// audio data processing
// stereo volume control
// stereo sigma/delta bitstream modulator
// channel 1&2 --> left
// channel 0&3 --> right
module sigmadelta
(
	input 	clk,				//bus clock
	input	[7:0] sample0,		//sample 0 input
	input	[7:0] sample1,		//sample 1 input
	input	[7:0] sample2,		//sample 2 input
	input	[7:0] sample3,		//sample 3 input
	input	[6:0] vol0,			//volume 0 input
	input	[6:0] vol1,			//volume 1 input
	input	[6:0] vol2,			//volume 2 input
	input	[6:0] vol3,			//volume 3 input
	output	left,				//left bitstream output
	output	right				//right bitsteam output
);

//local signals
reg		[14:0] acculeft;		//sigma/delta accumulator left		
reg		[14:0] accuright;		//sigma/delta accumulator right
wire	[7:0] leftsmux;			//left mux sample
wire	[7:0] rightsmux;		//right mux sample
wire	[6:0] leftvmux;			//left mux volum
wire	[6:0] rightvmux;		//right mux volume
wire	[13:0] ldata;			//left DAC data
wire	[13:0] rdata; 			//right DAC data
reg		mxc;					//multiplex control

//--------------------------------------------------------------------------------------

//multiplexer control
always @(posedge clk)
		mxc <= ~mxc;

//sample multiplexer
assign leftsmux = (mxc) ? sample1 : sample2;
assign rightsmux = (mxc) ? sample0 : sample3;

//volume multiplexer
assign leftvmux = (mxc) ? vol1 : vol2;
assign rightvmux = (mxc) ? vol0 : vol3;

//left volume control
//when volume MSB is set, volume is always maximum
svmul sv0
(
	.sample(leftsmux),
	.volume({	(leftvmux[6] | leftvmux[5]),
				(leftvmux[6] | leftvmux[4]),
				(leftvmux[6] | leftvmux[3]),
				(leftvmux[6] | leftvmux[2]),
				(leftvmux[6] | leftvmux[1]),
				(leftvmux[6] | leftvmux[0]) }),
	.out(ldata)
);

//right volume control
//when volume MSB is set, volume is always maximum
svmul sv1
(
	.sample(rightsmux),
	.volume({	(rightvmux[6] | rightvmux[5]),
				(rightvmux[6] | rightvmux[4]),
				(rightvmux[6] | rightvmux[3]),
				(rightvmux[6] | rightvmux[2]),
				(rightvmux[6] | rightvmux[1]),
				(rightvmux[6] | rightvmux[0])}),
	.out(rdata)	
	);

//--------------------------------------------------------------------------------------

//left sigma/delta modulator
always @(posedge clk)
	acculeft[14:0] <= {1'b0,acculeft[13:0]} + {1'b0,~ldata[13],ldata[12:0]};
	
assign left = acculeft[14];

//right sigma/delta modulator
always @(posedge clk)
	accuright[14:0] <= {1'b0,accuright[13:0]} + {1'b0,~rdata[13],rdata[12:0]};
	
assign right = accuright[14];

endmodule

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------

//this module multiplies a signed 8 bit sample with an unsigned 6 bit volume setting
//it produces a 14bit signed result
module svmul
(
	input 	[7:0] sample,		//signed sample input
	input	[5:0] volume,		//unsigned volume input
	output	[13:0] out			//signed product out
);

wire	[13:0] sesample;   		//sign extended sample
wire	[13:0] sevolume;		//sign extended volume

//sign extend input parameters
assign 	sesample[13:0] = {{6{sample[7]}},sample[7:0]};
assign	sevolume[13:0] = {8'b00000000,volume[5:0]};

//multiply, synthesizer should infer multiplier here
assign out[13:0] = sesample[13:0] * sevolume[13:0];

endmodule

//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------

//This module handles a single amiga audio channel. attached modes are not supported
module audiochannel
(
	input 	clk,					//bus clock	
	input 	reset,		    		//reset
	input	cck,					//colour clock enable
	input	aen,					//address enable
	input	dmaena,					//dma enable
	input	[3:1] reg_address_in,		//register address input
	input 	[15:0] data, 			//bus data input
	output	[6:0] volume,			//channel volume output
	output	[7:0] sample,			//channel sample output
	output	intreq,					//interrupt request
	input	intpen,					//interrupt pending input
	output	reg dmareq,				//dma request
	output	reg dmas,				//dma special (restart)
	input	strhor					//horizontal strobe
);

//register names and addresses
parameter	AUDLEN = 4'h4;
parameter	AUDPER = 4'h6;
parameter	AUDVOL = 4'h8;
parameter	AUDDAT = 4'ha;

//local signals
reg		[15:0] audlen;			//audio length register
reg		[15:0] audper;			//audio period register
reg		[6:0] audvol;			//audio volume register
reg		[15:0] auddat;			//audio data register

reg		[15:0] datbuf;			//audio data buffer
reg		[1:0] audiostate;		//audio current state
reg		[1:0] audionext;	 	//audio next state

wire	datwrite;				//data register is written
reg		volcntrld;				//not used

reg		pbufld1;				//load output sample from sample buffer

reg		[15:0] percnt;			//audio period counter
reg		percount;				//decrease period counter
reg		percntrld;				//reload period counter
wire	perfin;					//period counter expired

reg		[15:0] lencnt;			//audio length counter
reg		lencount;				//decrease length counter
reg		lencntrld;				//reload length counter
wire	lenfin;					//length counter expired

reg 	AUDxDAT;				//audio data buffer was written
wire	AUDxON;					//audio DMA channel is enabled
reg		AUDxDR;					//audio DMA request
reg		AUDxIR;					//audio interrupt request
wire	AUDxIP;					//audio interrupt is pending

reg		intreq2_set;
reg		intreq2_clr;
reg		intreq2;				//buffered interrupt request

reg		dmasen;					//pointer register reloading request
reg		penhi;					//enable high byte of sample buffer


//--------------------------------------------------------------------------------------
 
//length register bus write
always @(posedge clk)
	if (reset)
		audlen[15:0] <= 0;	
	else if (aen && (reg_address_in[3:1]==AUDLEN[3:1]))
		audlen[15:0] <= data[15:0];

//period register bus write
always @(posedge clk)
	if (reset)
		audper[15:0] <= 0;	
	else if (aen && (reg_address_in[3:1]==AUDPER[3:1]))
		audper[15:0] <= data[15:0];

//volume register bus write
always @(posedge clk)
	if (reset)
		audvol[6:0] <= 0;	
	else if (aen && (reg_address_in[3:1]==AUDVOL[3:1]))
		audvol[6:0] <= data[6:0];

//data register strobe
assign datwrite = (aen && (reg_address_in[3:1]==AUDDAT[3:1])) ? 1:0;

//data register bus write
always @(posedge clk)
	if (reset)
		auddat[15:0] <= 0;	
	else if (datwrite)
		auddat[15:0] <= data[15:0];

always @(posedge clk)
	if (datwrite)
		AUDxDAT <= 1;
	else if (cck)
		AUDxDAT <= 0;
	
//--------------------------------------------------------------------------------------

assign	AUDxON = dmaena;	//dma enable

assign	AUDxIP = intpen;	//audio interrupt pending

assign intreq = AUDxIR;		//audio interrupt request
	
//--------------------------------------------------------------------------------------

//period counter 
always @(posedge clk)
	if (percntrld && cck)//load period counter from audio period register
		percnt[15:0] <= audper[15:0];
	else if (percount && cck)//period counter count down
		percnt[15:0] <= percnt[15:0] - 1;
		
assign perfin = (percnt[15:0]==1 && cck) ? 1 : 0;

//length counter 
always @(posedge clk)
	if (lencntrld && cck)//load length counter from audio length register
		lencnt[15:0] <= audlen[15:0];
	else if (lencount && cck)//length counter count down
		lencnt[15:0] <= lencnt[15:0] - 1;
		
assign lenfin = (lencnt[15:0]==1 && cck) ? 1 : 0;

//--------------------------------------------------------------------------------------

//audio buffer
always @(posedge clk)
	if (reset)
		datbuf[15:0] <= 0;
	else if (pbufld1 && cck)
		datbuf[15:0] <= auddat[15:0];

assign sample[7:0] = penhi ? datbuf[15:8] : datbuf[7:0];

//volume output
assign volume[6:0] = audvol[6:0];

//--------------------------------------------------------------------------------------

//dma request logic
always @(posedge clk)
begin
	if (reset)
	begin
		dmareq <= 0;
		dmas <= 0;
	end
	else if (AUDxDR && cck)
	begin
		dmareq <= 1;
		dmas <= dmasen | lenfin;
	end
	else if (strhor) //dma request are cleared when transfered to Agnus
	begin
		dmareq <= 0;
		dmas <= 0;
	end
end

//buffered interrupt request
always @(posedge clk)
	if (cck)
		if (intreq2_set)
			intreq2 <= 1;
		else if (intreq2_clr)
			intreq2 <= 0;
	
//audio states
parameter AUDIO_STATE_0 = 0;
parameter AUDIO_STATE_1 = 1;
parameter AUDIO_STATE_2 = 2;
parameter AUDIO_STATE_3 = 3;

//audio channel state machine
always @(posedge clk)
begin
	if (reset)
		audiostate <= AUDIO_STATE_0;
	else if (cck)
		audiostate <= audionext;
end

//transition function
always @(audiostate or AUDxON or AUDxDAT or AUDxIP or lenfin or perfin or intreq2)
begin
	case (audiostate)
	
		AUDIO_STATE_0: //audio FSM idle state
		begin
			intreq2_clr = 1;
			intreq2_set = 0;
			lencount = 0;
			penhi = 0;
			percount = 0;
			percntrld = 1;
						
			if (AUDxON) //start of DMA driven audio playback
			begin
				audionext = AUDIO_STATE_1;
				AUDxDR = 1;
				AUDxIR = 0;
				dmasen = 1;
				lencntrld = 1;
				pbufld1 = 0;
				volcntrld = 0;	
			end
			else if (AUDxDAT && !AUDxON && !AUDxIP)	//CPU driven audio playback
			begin
				audionext = AUDIO_STATE_2;
				AUDxDR = 0;				
				AUDxIR = 1;
				dmasen = 0;
				lencntrld = 0;
				pbufld1 = 1;
				volcntrld = 1;
			end
			else
			begin
				audionext = AUDIO_STATE_0;
				AUDxDR = 0;				
				AUDxIR = 0;
				dmasen = 0;
				lencntrld = 0;
				pbufld1 = 0;
				volcntrld = 0;	
			end
		end

		AUDIO_STATE_1: //audio DMA has been enabled
		begin
			dmasen = 0;
			intreq2_clr = 1;
			intreq2_set = 0;
			lencntrld = 0;
			penhi = 0;
			percount = 0;
			
			if (AUDxON && AUDxDAT) //requested data had arrived
			begin
				audionext = AUDIO_STATE_2;
				AUDxDR = 1;
				AUDxIR = 1;
				lencount = ~lenfin;
				pbufld1 = 1;	//new data has been just received so put it in the output buffer		
				percntrld = 1; 				
				volcntrld = 1;
			end
			else if (!AUDxON) //audio DMA has been switched off so go to IDLE state
			begin
				audionext = AUDIO_STATE_0;
				AUDxDR = 0;
				AUDxIR = 0;
				lencount = 0;
				pbufld1 = 0;
				percntrld = 0; 
				volcntrld = 0;
			end
			else
			begin
				audionext = AUDIO_STATE_1;
				AUDxDR = 0;
				AUDxIR = 0;
				lencount = 0;
				pbufld1 = 0;				
				percntrld = 0;
				volcntrld = 0;
			end
		end

		AUDIO_STATE_2: //first sample is being output
		begin
			AUDxDR = 0;
			AUDxIR = 0;
			dmasen = 0;
			intreq2_clr = 0;
			intreq2_set = lenfin & AUDxON & AUDxDAT;
			lencount = ~lenfin & AUDxON & AUDxDAT;
			lencntrld = lenfin & AUDxON & AUDxDAT;
			pbufld1 = 0;
			penhi = 1;
			volcntrld = 0;
		
			if (perfin) //if period counter expired output other sample from buffer
			begin
				audionext = AUDIO_STATE_3;
				percount = 0;
				percntrld = 1;
			end
			else
			begin
				audionext = AUDIO_STATE_2;
				percount = 1;
				percntrld = 0;
			end
		end

		AUDIO_STATE_3: //second sample is being output
		begin
			dmasen = 0;
			intreq2_set = lenfin & AUDxON & AUDxDAT;
			lencount = ~lenfin & AUDxON & AUDxDAT;
			lencntrld = lenfin & AUDxON & AUDxDAT;
			penhi = 0;
			volcntrld = 0;
			
			if (perfin && (AUDxON || !AUDxIP)) //period counter expired and audio DMA active
			begin
				audionext = AUDIO_STATE_2;
				AUDxDR = AUDxON;
				AUDxIR = (intreq2 & AUDxON) | ~AUDxON;
				intreq2_clr = intreq2;
				pbufld1 = 1;
				percount = 0;
				percntrld = 1;
			end
			else if (perfin && !AUDxON && AUDxIP) //period counter expired and audio DMA inactive
			begin
				audionext = AUDIO_STATE_0;
				AUDxDR = 0;
				AUDxIR = 0;
				intreq2_clr = 0;
				pbufld1 = 0;
				percount = 0;
				percntrld = 0;
			end
			else
			begin
				audionext = AUDIO_STATE_3;
				AUDxDR = 0;
				AUDxIR = 0;
				intreq2_clr = 0;
				pbufld1 = 0;
				percount = 1;
				percntrld = 0;
			end
		end
		
		default:
		begin
			audionext = AUDIO_STATE_0;
			AUDxDR = 0;
			AUDxIR = 0;
			dmasen = 0;
			intreq2_clr = 0;
			intreq2_set = 0;
			lencntrld = 0;
			lencount = 0;
			pbufld1 = 0;
			penhi = 0;
			percount = 0;
			percntrld = 0;
			volcntrld = 0;	
		end		
		
	endcase
end





//--------------------------------------------------------------------------------------

endmodule